package com.song.tools.generator;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler;
import com.baomidou.mybatisplus.generator.query.SQLQuery;
import com.song.tools.generator.config.CustomTemplateConfig;
import com.song.tools.generator.config.EnumValueConfig;
import com.song.tools.generator.config.GeneratorConfig;
import com.song.tools.generator.util.Field;
import com.song.tools.generator.util.StringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

import static com.song.tools.generator.util.Constant.FieldNameConstants.*;

/**
 * 代码生成
 *
 * @author song tools
 * @since 2024-06-24
 */
@Slf4j
public class CodeGenerator {

    private final FreemarkerTemplateEngine templateEngine;

    public CodeGenerator() {
        this.templateEngine = new FreemarkerTemplateEngine();
    }

    public void execute(GeneratorConfig generatorConfig) {
        // 数据库配置
        DataSourceConfig dataSourceConfig = buildDataSourceConfig(generatorConfig);

        // 全局配置
        GlobalConfig globalConfig = buildGlobalConfig(generatorConfig);

        // 包配置+文件生成路径配置
        PackageConfig packageConfig = buildPackageConfig(generatorConfig);

        // 模版配置
        StrategyConfig strategyConfig = buildStrategyConfig(generatorConfig);
        InjectionConfig injectionConfig = buildInjectionConfig(generatorConfig);

        // 初始化配置
        ConfigBuilder config = new ConfigBuilder(packageConfig, dataSourceConfig, strategyConfig, null, globalConfig, injectionConfig);
        templateEngine.setConfigBuilder(config);
        templateEngine.init(config).batchOutput();
    }


    private DataSourceConfig buildDataSourceConfig(GeneratorConfig config) {
        // 数据库配置
        return new DataSourceConfig.Builder(config.getMysqlConnectionConfig().getUrl(), config.getMysqlConnectionConfig().getUsername(), config.getMysqlConnectionConfig().getPassword())
                .dbQuery(new MySqlQuery())
                .schema("mybatis-plus")
//                .typeConvert(new CustomTypeConvert(configuration))
                .keyWordsHandler(new MySqlKeyWordsHandler())
                .databaseQueryClass(SQLQuery.class)
                .build();
    }

    private GlobalConfig buildGlobalConfig(GeneratorConfig config) {
        // 全局配置
        GlobalConfig.Builder globalConfigBuilder = new GlobalConfig.Builder()
                .outputDir(config.getProjectDir() + "src/main/java")
                .author(config.getAuthor())
                .dateType(DateType.TIME_PACK)
                .commentDate("yyyy-MM-dd");
        if (config.getEnableSwagger()) {
            globalConfigBuilder.enableSwagger();
        }
        if (config.getEnableSpringDoc()) {
            globalConfigBuilder.enableSpringdoc();
        }

        return globalConfigBuilder.build();
    }

    private PackageConfig buildPackageConfig(GeneratorConfig config) {
        return new PackageConfig.Builder()
                .parent(config.getParent())
                .moduleName(config.getModuleName())
                .entity(String.format("dao.%s.entity", config.getModuleName()))
                .mapper(String.format("dao.%s.mapper", config.getModuleName()))
                .xml(String.format("mapper.%s.xml", config.getModuleName()))
                // 配置xml文件路径
                .pathInfo(Collections.singletonMap(OutputFile.xml, config.getProjectDir() + "src/main/resources/mapper/"))
                .build();
    }


    private StrategyConfig buildStrategyConfig(GeneratorConfig config) {
        // 模版位置对应的是mybatis-plus-generator-3.5.7.jar包中的模板
        return new StrategyConfig.Builder()
                .enableSkipView() // 禁用视图过滤
                .addInclude(config.getTableName()) // 指定表
                .addTablePrefix(config.getTablePrefixList()) // 增加过滤表前缀
                .entityBuilder()
                //.superClass("com.song.tools.core.entity.SuperEntity")
                .enableLombok() // 启用 Lombok
                .javaTemplate("/templates/entity.java") // 设置实体类模板
                //.addSuperEntityColumns("id", "create_user_id", "create_username", "create_time", "update_user_id", "update_username", "update_time", "deleted") // 添加父类公共字段
                .disable() // 禁用实体
                .idType(IdType.AUTO)
                .mapperBuilder()
                .enableBaseColumnList()
                .enableBaseResultMap()
                .mapperTemplate("/templates/mapper.java") // 设置 Mapper 模板
                .disableMapper()
                .mapperXmlTemplate("/templates/mapper.xml")
//                .disableMapperXml()
                .serviceBuilder()
                .disableService() // 禁用service
                .disableServiceImpl() // 禁用serviceImpl
                .controllerBuilder()
                .disable() // 禁用controller
                .build();
    }


    private InjectionConfig buildInjectionConfig(GeneratorConfig config) {
        // 自定义模版中使用的变量
        InjectionConfig.Builder injectionConfig = new InjectionConfig.Builder().beforeOutputFile(((tableInfo, objectMap) -> this.appendCustomObject(config, tableInfo, objectMap)));

        // 自定义模版
        List<CustomTemplateConfig> configList = new ArrayList<>();
        configList.add(new CustomTemplateConfig("Query.java", "/entity/Query.java.ftl", "beans.query"));
        configList.add(new CustomTemplateConfig("Manager.java", "Manager.java.ftl", "manager"));
        configList.add(new CustomTemplateConfig("Bo.java", "/entity/Bo.java.ftl", "beans.bo"));
        configList.add(new CustomTemplateConfig("QueryRequest.java", "/entity/QueryRequest.java.ftl", "beans.request"));
        configList.add(new CustomTemplateConfig("SaveRequest.java", "/entity/SaveRequest.java.ftl", "beans.request"));
        configList.add(new CustomTemplateConfig("UpdateRequest.java", "/entity/UpdateRequest.java.ftl", "beans.request"));
        configList.add(new CustomTemplateConfig("DeleteResponse.java", "/entity/DeleteResponse.java.ftl", "beans.response"));
        configList.add(new CustomTemplateConfig("QueryResponse.java", "/entity/QueryResponse.java.ftl", "beans.response"));
        configList.add(new CustomTemplateConfig("SaveResponse.java", "/entity/SaveResponse.java.ftl", "beans.response"));
        configList.add(new CustomTemplateConfig("UpdateResponse.java", "/entity/UpdateResponse.java.ftl", "beans.response"));
        configList.add(new CustomTemplateConfig("ExportDto.java", "/entity/ExportDto.java.ftl", "beans.poi"));
        configList.add(new CustomTemplateConfig("ImportDto.java", "/entity/ImportDto.java.ftl", "beans.poi"));
        configList.add(new CustomTemplateConfig("Convertor.java", "Convertor.java.ftl", "convertor"));
        configList.add(new CustomTemplateConfig("Convertor.java", "Convertor.java.ftl", "convertor"));
        configList.add(new CustomTemplateConfig("Service.java", "Service.java.ftl", "service"));
        configList.add(new CustomTemplateConfig("ServiceImpl.java", "ServiceImpl.java.ftl", "service.impl"));
        configList.add(new CustomTemplateConfig("Controller.java", "Controller.java.ftl", "controller"));
        configList.add(new CustomTemplateConfig(".java", "/entity/Entity.java.ftl", "dao.entity"));
        configList.add(new CustomTemplateConfig("Mapper.java", "Mapper.java.ftl", "dao.mapper"));
        // configList.add(new CustomTemplateConfig("Mapper.xml", "Mapper.xml.ftl", "dao.mapper"));
        for (CustomTemplateConfig templateConfig : configList) {
            injectionConfig.customFile(new CustomFile.Builder()
                    .fileName(templateConfig.getFileName())
                    .templatePath(config.getTemplate() + "/" + templateConfig.getTemplatePath())
                    .packageName(templateConfig.getPackageName())
                    .enableFileOverride()
                    .build()
            );
        }

        return injectionConfig.build();
    }

    @SuppressWarnings("unchecked")
    private void appendCustomObject(GeneratorConfig config, TableInfo tableInfo, Map<String, Object> objectMap) {
        // 对象首字母消息，用于变量名称
        objectMap.put("entityLowerCaseFirstOne", StringUtil.toLowerCaseFirstOne(objectMap.get("entity").toString()));
        objectMap.put("primaryKeyNameUpperCaseFirstOne", StringUtil.toUpperCaseFirstOne(config.getPrimaryKeyName()));
        objectMap.put("primaryKeyName", config.getPrimaryKeyName());
        objectMap.put("stringStr", "String");

        // 是否生成poiService的实现
        objectMap.put("poiServiceImpl", config.getPoiServiceImpl());
        objectMap.put("superMapperClassPackage", "com.song.tools.mvc.mapper");

        // 设置包名
        Map<String, String> packageMap = (Map<String, String>) objectMap.get("package");
        packageMap = new HashMap<>(packageMap);
        packageMap.put("QueryRequest", packageMap.get("Parent") + "." + "beans.request");
        packageMap.put("QueryResponse", packageMap.get("Parent") + "." + "beans.response");
        packageMap.put("SaveRequest", packageMap.get("Parent") + "." + "beans.request");
        packageMap.put("SaveResponse", packageMap.get("Parent") + "." + "beans.response");
        packageMap.put("UpdateRequest", packageMap.get("Parent") + "." + "beans.request");
        packageMap.put("UpdateResponse", packageMap.get("Parent") + "." + "beans.response");
        packageMap.put("DeleteResponse", packageMap.get("Parent") + "." + "beans.response");
        packageMap.put("Service", packageMap.get("Parent") + "." + "service");
        packageMap.put("ServiceImpl", packageMap.get("Parent") + "." + "service.impl");
        packageMap.put("Bo", packageMap.get("Parent") + "." + "beans.bo");
        packageMap.put("Query", packageMap.get("Parent") + "." + "beans.query");
        packageMap.put("Manager", packageMap.get("Parent") + "." + "manager");
        packageMap.put("Convertor", packageMap.get("Parent") + "." + "convertor");
        packageMap.put("Controller", packageMap.get("Parent") + "." + "controller");
        packageMap.put("Entity", packageMap.get("Parent") + "." + "dao.entity");
        packageMap.put("Mapper", packageMap.get("Parent") + "." + "dao.mapper");
        packageMap.put("ImportDto", packageMap.get("Parent") + "." + "beans.poi");
        packageMap.put("ExportDto", packageMap.get("Parent") + "." + "beans.poi");
        packageMap.put("Enum", packageMap.get("Parent") + "." + "common.constant");

        objectMap.put("package", packageMap);

        objectMap.put("PrimaryKeyType", config.getPrimaryKeyType());
        objectMap.put("customSuperEntityClass", "true");

        // 设置类名
        objectMap.put("QueryRequestName", tableInfo.getEntityName() + "QueryRequest");
        objectMap.put("QueryResponseName", tableInfo.getEntityName() + "QueryResponse");
        objectMap.put("SaveRequestName", tableInfo.getEntityName() + "SaveRequest");
        objectMap.put("SaveResponseName", tableInfo.getEntityName() + "SaveResponse");
        objectMap.put("UpdateRequestName", tableInfo.getEntityName() + "UpdateRequest");
        objectMap.put("UpdateResponseName", tableInfo.getEntityName() + "UpdateResponse");
        objectMap.put("DeleteResponseName", tableInfo.getEntityName() + "DeleteResponse");
        objectMap.put("ExportDtoName", tableInfo.getEntityName() + "ExportDto");
        objectMap.put("ImportDtoName", tableInfo.getEntityName() + "ImportDto");

        objectMap.put("BoName", tableInfo.getEntityName() + "Bo");
        objectMap.put("QueryName", tableInfo.getEntityName() + "Query");
        objectMap.put("ManagerName", tableInfo.getEntityName() + "Manager");
        objectMap.put("ConvertorName", tableInfo.getEntityName() + "Convertor");
        objectMap.put("ServiceName", tableInfo.getEntityName() + "Service");
        objectMap.put("ServiceImplName", tableInfo.getEntityName() + "ServiceImpl");
        objectMap.put("ControllerName", tableInfo.getEntityName() + "Controller");
        objectMap.put("EntityName", tableInfo.getEntityName());

        // entity字段
        List<Field> allFields = tableInfo.getFields().stream().map(field -> Field.buildByTableFieldWithEnumMap(field, config.getEnumNameMap())).collect(Collectors.toList());
        objectMap.put("EntityFields", allFields);
        objectMap.put("EntityBoFields", allFields);
        objectMap.put("QueryFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USERNAME, CREATE_USER, UPDATE_USER_ID, UPDATE_USERNAME, UPDATE_USER).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("QueryRequestFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USERNAME, CREATE_USER, UPDATE_USERNAME, UPDATE_USER, UPDATE_USER_ID).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("QueryResponseFields", allFields.stream().filter(item -> !Objects.equals(DELETED, item.getName())).collect(Collectors.toList()));
        objectMap.put("SaveRequestFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, ID, CREATE_TIME, UPDATE_USER_ID, UPDATE_USERNAME, UPDATE_USER, UPDATE_TIME).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("SaveResponseFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USERNAME, CREATE_USER, UPDATE_USERNAME, UPDATE_USER, UPDATE_TIME).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("UpdateRequestFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USER_ID, CREATE_USERNAME, CREATE_USER, CREATE_TIME, UPDATE_USER_ID, UPDATE_USERNAME, UPDATE_USER, UPDATE_TIME).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("UpdateResponseFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USER, UPDATE_USERNAME, CREATE_TIME, UPDATE_USERNAME, UPDATE_USER).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("ImportFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USER_ID, CREATE_USER, UPDATE_USERNAME, UPDATE_USER_ID, UPDATE_USERNAME, UPDATE_USER).contains(item.getName())).collect(Collectors.toList()));
        objectMap.put("ExportFields", allFields.stream().filter(item -> !Arrays.asList(DELETED, CREATE_USER_ID, CREATE_USER, UPDATE_USERNAME, UPDATE_USER_ID, UPDATE_USERNAME, UPDATE_USER).contains(item.getName())).collect(Collectors.toList()));

        // 生成枚举
        List<Field> enumFields = allFields.stream().filter(Field::isEnum).toList();
        generateEnum(enumFields, config, objectMap);
    }

    @SneakyThrows
    @SuppressWarnings("all")
    private void generateEnum(List<Field> enumFields, GeneratorConfig config, Map<String, Object> objectMap) {
        List<String> enumPackages = new ArrayList<>();
        objectMap.put("enumImportPackages", enumPackages);
        for (Field enumField : enumFields) {
            String comment = enumField.getComment();
            Map<String, String> enumNameMap = config.getEnumNameMap();
            String enumClassName = enumNameMap.get(enumField.getPropertyName());
            objectMap.put("EnumName", enumClassName);
            objectMap.put("lowerFirstCharEnumName", StringUtil.toLowerCaseFirstOne(enumClassName));
            parseEnumComment(enumField, comment, objectMap);

            Map<String, String> packageMap = (Map<String, String>) objectMap.get("package");
            String filePath = packageMap.get("Enum").replaceAll("\\.", "/");
            String fileName = config.getProjectDir() + "src/main/java/" + filePath + "/" + enumClassName + ".java";
            enumPackages.add(packageMap.get("Enum") + "." + enumClassName);
            File file = new File(fileName);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            file.createNewFile();
            templateEngine.writer(objectMap, "/mybatis-templates/Enum.java.ftl", file);
        }
    }


    private void parseEnumComment(Field enumField, String comment, Map<String, Object> objectMap) {
        EnumValueConfig enumValueConfig = new EnumValueConfig(enumField, comment);
        objectMap.put("enumCodeAndDescList", enumValueConfig.getEnumValues());
        objectMap.put("enumChineseName", enumValueConfig.getEnumChineseName());
        objectMap.put("enumCodeType", enumValueConfig.getCodeType());
    }
}
